跳到主要内容

包管理工具( Pnpm )

pnpm 英文 performant npm,即高性能的 npm,在使用上几乎和 npm 一样,只需要把 npm 改为 pnpm 即可。那它的高性能体现在哪里呢?

有以下优势:

  • 安装依赖的效率更快,远远超过 yarn 和 npm
  • 极大降低存储空间的占用
  • 简洁合理的 node_modules 目录结构
  • 避免幽灵依赖的引用

依赖管理(node_modules)​

node_modules 其实是包管理工具的一个产物,它的作用我总结了两点:

  • 依赖存储:node_modules 用来存储依赖包,至于怎么管理依赖,这是包管理工具做的事情
  • 依赖导入:依赖包可作为第三方模块给项目提供一些功能块,提高我们的开发效率。至于怎么打包这些依赖包,构建目标环境的产物,这是打包工具需要做的事情

所以我们开发项目是需要包管理工具和打包工具来共同完成的,常见的打包工具有 webpack、rollup、gulp 等

依赖导入​

模块的导入,有两种规则 Node 和 Classic,以 Node 为例

  • 先对 require 参数 加「文件后缀」,去 ./node_modules 下匹配文件,匹配到了,就去执行文件,并返回 module.exports 内容。

  • 上一步匹配失败,就去匹配「目录」

    • 匹配到目录,读取该目录下 package.json文件的 main对应的文件

      • 存在 main 对应的文件,就根据路径找到文件执行,并返回 module.exports 内容。若 main 对应的文件不存在,Node 会给一个 DeprecationWarning 提示信息,返回一个空对象
  • 不存在 main,默认查找目录下的 index.js 文件:存在 index.js 文件,就执行文件,并返回 module.exports 内容;不存在 index.js 文件,就报错 Error: Cannot find module 'xxxx'

  • 没有匹配到目录,就在当前项目的「上级目录」下,查找是否存在 node_modules 目录:存在就重复上述步骤;否则就往上级目录依次继续查找 node_modules 目录,若最终在顶级目录依然没找到模块,Node 就会报错

/Users/Documents/project/node_modules/module.js
/Users/Documents/project/node_modules/module/package.json (如果指定了"main"属性,找对应的文件)
/Users/Documents/project/node_modules/module/index.js

/Users/Documents/node_modules/module.js
/Users/Documents/node_modules/module/package.json (如果指定了"main"属性,找对应的文件)
/Users/Documents/node_modules/module/index.js

/Users/node_modules/module.js
/Users/node_modules/module/package.json (如果指定了"main"属性,找对应的文件)
/Users/node_modules/module/index.js

/node_modules/module.js
/node_modules/module/package.json (如果指定了"main"属性,找对应的文件)
/node_modules/module/index.js

都找不到就报错

依赖如何导入的其实也间接影响了依赖怎么存储的。因为依赖存储的意义就是供当前项目解析使用的。

依赖存储​

在了解依赖怎么存储前,先了解两个概念

  • 直接依赖:当前项目 package.json 中的依赖,也可称为顶级依赖
  • 间接依赖:第三方包中的依赖,也可以认为除了 package.json 中的依赖,其他都是

npm、yarn​

扁平化的处理方式,虽然解决包的嵌套过深问题,但当安装包出现版本冲突时,解决方案很鸡肋,但也只能这样。下面总结了几点安装依赖会出现的问题:

  • 若不存在依赖冲突就将依赖扁平化下载到 node_modules,层级最多为 2 层,即「项目根目录的 node_modules、依赖包的 node_modules」
  • 存在依赖冲突
    • 若安装直接依赖时,和间接依赖出现冲突,直接依赖会顶掉之前的依赖,之前的依赖会放回依赖包的 node_modules 目录下
    • 若安装间接依赖时,和直接依赖出现冲突,直接依赖优先级高,间接依赖会放到依赖包的 node_modules 目录下
    • 若安装间接依赖出现冲突,高版本的依赖会放到根 node_modules 下,低版本的放到依赖包的 node_modules 目录下

npm 和 yarn 这种 install 方式,其实存在很多其他的问题:

  1. 间接依赖的使用,正常情况下,我们是不可以使用的,但这种扁平化的方式,使得我们也可以使用这些依赖
  2. 安装效率差,虽然有缓存的机制,但每次都需要去从缓存中拷贝依赖,放到本地项目环境中,还是有些消耗效率的
  3. 硬盘存储空间占用过大,会拷贝依赖到当前的项目中,如果我们很多个项目安装这个依赖,就会导致这个依赖重复的存在每个项目中,占用硬盘的空间
  4. node_modules 构建过于复杂,因为 node_modules 目录下会存放着所有的依赖,不怎么好阅读
  5. 不同版本的依赖包安装会出现冲突,处理方式不是特别的好。如果多个相同版本的间接依赖和直接依赖冲突时,间接依赖会分别安装到每个依赖包中,浪费了存储空间

Pnpm 包管理器就很好地解决了这些问题。

pnpm​

先了解下 hard link 和 symbolic link

文件的本质是什么?我们看到的文件,其实类似一个指针,指向文件的真实数据,我们打开文件时,计算机会读取文件指向的真实数据。

hard link 相当于拷贝了文件的指针,拷贝后这两个指针没有关系,就是删除其中一个文件,对另一个文件没有什么影响,它并不会删除文件对应的数据。但两个文件指向的是同一份数据,修改任意一个文件内容时,两个文件对应的数据都会被改变。hard link 只能通过文件来创建。

symbolic link 基于文件创建了一个文件指针,和 hard link 不同的是,symbolic link 创建的文件指针,指向创建它的原文件,删除原文件,symbolic link 也会失效。symbolic link 就相当于快捷方式,使我们能快捷读取计算机的文件。symbolic link 还可以通过文件夹来创建。

pnpm 的包管理机制就是基于 hard link 和 symbolic link 来实现的,hard link 保证相同版本的依赖在计算机只存在一份,symbolic link 保证了可以查找到对应依赖,结构比较复杂,我通过项目来说明

  • 直接依赖 和 间接依赖统一放在 ./node_modules/.pnpm/,每个依赖目录的存储格式是 包名@版本号。如果一个包有多个版本可以通过这种命名方式区分开。

  • 项目只能引用直接依赖,即 ./node_modules 下的依赖,这些依赖往往是一些 symbolic link。这些包的原目录是 ./node_modules/.pnpm/包名@版本号/node_modules/包名。

    • 比如 ./node_modules/react 的源目录是./node_modules/.pnpm/react@17.0.2/node_modules/react。./node_modules/.pnpm 目录下的包通常是通过 Store 创建的 hard link
  • 直接依赖和间接依赖本身依赖的包,放在 ./node_modules/.pnpm/包名@版本号/node_modules 下,除了本身,其他包也都是一些 symbolic link,通过./node_modules/.pnpm 创建的

  • 通过 hard link 和 Store 都可修改包的文件本身内容。如果修改某一文件,其实也没什么用,下次再安装该包时,会重新下载远程包,覆盖掉已修改的内容;如果未修改包的内容,则再次下载,包的所有文件会基于 Store 缓存的文件创建 hard link 到本地项目。所以不要修改 Store 创建 hard link 的包文件

Alt text